<template>
  <div class="editable-element-text" :class="{ 'lock': elementInfo.lock }" :style="{
    top: elementInfo.top + 'px',
    left: elementInfo.left + 'px',
    width: elementInfo.width + 'px',
    height: elementInfo.height + 'px',
  }">
    <div class="rotate-wrapper" :style="{ transform: `rotate(${elementInfo.rotate}deg)` }">
      <div class="element-content" ref="elementRef" :style="{
        width: elementInfo.vertical ? 'auto' : elementInfo.width + 'px',
        height: elementInfo.vertical ? elementInfo.height + 'px' : 'auto',
        backgroundColor: elementInfo.fill,
        opacity: elementInfo.opacity,
        textShadow: shadowStyle,
        lineHeight: elementInfo.lineHeight,
        letterSpacing: (elementInfo.wordSpace || 0) + 'px',
        color: elementInfo.defaultColor,
        fontFamily: elementInfo.defaultFontName,
        writingMode: elementInfo.vertical ? 'vertical-rl' : 'horizontal-tb',
      }" v-contextmenu="contextmenu" @mousedown="$event => handleSelectElement($event)">
        <ElementOutline :width="elementInfo.width" :height="elementInfo.height" :outline="elementInfo.outline" />
        <ProsemirrorEditor class="text" :elementId="elementInfo.id" :defaultColor="elementInfo.defaultColor"
          :defaultFontName="elementInfo.defaultFontName" :editable="!elementInfo.lock" :value="elementInfo.content"
          :style="{
            '--textIndent': `${elementInfo.textIndent || 0}px`,
            '--paragraphSpace': `${elementInfo.paragraphSpace === undefined ? 5 : elementInfo.paragraphSpace}px`,
          }" @update="value => updateContent(value)" @mousedown="$event => handleSelectElement($event, false)" />

        <!-- 当字号过大且行高较小时，会出现文字高度溢出的情况，导致拖拽区域无法被选中，因此添加了以下节点避免该情况 -->
        <div class="drag-handler top"></div>
        <div class="drag-handler bottom"></div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { defineProps, computed, onMounted, onUnmounted, PropType, ref, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { debounce } from 'lodash'
import { PPTTextElement } from '@/types/slides'
import { ContextmenuItem } from '@/components/Contextmenu/types'
import { useMainStore } from '@/store/main'
import { useSlidesStore } from '@/store/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import useElementShadow from '../hooks/useElementShadow'

import ElementOutline from '@/views/components/element/ElementOutline.vue'
import ProsemirrorEditor from '@/views/components/element/ProsemirrorEditor.vue'

const props = defineProps({
  elementInfo: {
    type: Object as PropType<PPTTextElement>,
    required: true,
  },
  selectElement: {
    type: Function as PropType<(e: MouseEvent | TouchEvent, element: PPTTextElement, canMove?: boolean) => void>,
    required: true,
  },
  contextmenu: {
    type: Function as PropType<() => ContextmenuItem[] | null>,
  },
})

const mainStore = useMainStore()
const slidesStore = useSlidesStore()
const { handleElementId, isScaling } = storeToRefs(mainStore)

const { addHistorySnapshot } = useHistorySnapshot()

const elementRef = ref<HTMLElement>()

const shadow = computed(() => props.elementInfo.shadow)
const { shadowStyle } = useElementShadow(shadow)

const handleSelectElement = (e: MouseEvent, canMove = true) => {
  if (props.elementInfo.lock) return
  e.stopPropagation()

  props.selectElement(e, props.elementInfo, canMove)
}

/**
 * 监听文本元素的尺寸变化，当高度变化时，更新高度到vuex
 * 若高度变化时正处在缩放操作中，则等待缩放操作结束后再更新
 */
const realHeightCache = ref(-1)
const realWidthCache = ref(-1)

watch(isScaling, () => {
  if (handleElementId.value !== props.elementInfo.id) return

  if (!isScaling.value) {
    if (!props.elementInfo.vertical && realHeightCache.value !== -1) {
      slidesStore.updateElement({
        id: props.elementInfo.id,
        props: { height: realHeightCache.value }
      })
      realHeightCache.value = -1
    }
    if (props.elementInfo.vertical && realWidthCache.value !== -1) {
      slidesStore.updateElement({
        id: props.elementInfo.id,
        props: { width: realWidthCache.value },
      })
      realWidthCache.value = -1
    }
  }
})

// entries：是一个 ResizeObserverEntry 对象数组，可以用于获取每个元素改变后的新尺寸。
const updateTextElementHeight = (entries: ResizeObserverEntry[]) => {
  const contentRect = entries[0].contentRect
  if (!elementRef.value) return

  const realHeight = contentRect.height + 20
  const realWidth = contentRect.width + 20

  if (!props.elementInfo.vertical && props.elementInfo.height !== realHeight) {
    if (!isScaling.value) {
      slidesStore.updateElement({
        id: props.elementInfo.id,
        props: { height: realHeight }
      })
    }
    else realHeightCache.value = realHeight
  }
  if (props.elementInfo.vertical && props.elementInfo.width !== realWidth) {
    if (!isScaling.value) {
      slidesStore.updateElement({
        id: props.elementInfo.id,
        props: { width: realWidth },
      })
    }
    else realWidthCache.value = realWidth
  }
}
// ResizeObserver 接口监视 Element 内容盒或边框盒或者 SVGElement 边界尺寸的变化。
// 每当elementRef元素发生变化时，则调用updateTextElementHeight
const resizeObserver = new ResizeObserver(updateTextElementHeight)
onMounted(() => {
  if (elementRef.value) resizeObserver.observe(elementRef.value)
})
onUnmounted(() => {
  if (elementRef.value) resizeObserver.unobserve(elementRef.value)
})

const updateContent = (content: string) => {
  slidesStore.updateElement({
    id: props.elementInfo.id,
    props: { content }
  })
  addHistorySnapshot()
}

const checkEmptyText = debounce(function () {
  const pureText = props.elementInfo.content.replaceAll(/<[^>]+>/g, '')
  if (!pureText) slidesStore.deleteElement(props.elementInfo.id)
}, 300, { trailing: true })

const isHandleElement = computed(() => handleElementId.value === props.elementInfo.id)
watch(isHandleElement, () => {
  if (!isHandleElement.value) checkEmptyText()
})
</script>

<style lang="scss" scoped>
.editable-element-text {
  position: absolute;

  &.lock .element-content {
    cursor: default;
  }
}

.rotate-wrapper {
  width: 100%;
  height: 100%;
}

.element-content {
  position: relative;
  padding: 10px;
  line-height: 1.5;
  word-break: break-word;
  cursor: move;

  .text {
    position: relative;
  }

  ::v-deep(a) {
    cursor: text;
  }
}

.drag-handler {
  height: 10px;
  position: absolute;
  left: 0;
  right: 0;

  &.top {
    top: 0;
  }

  &.bottom {
    bottom: 0;
  }
}
</style>